﻿using System;
using System.Web.Mvc;
using System.Web.Routing;

namespace MVCHelperExtensions
{
  /// <summary>
  /// Static class that contains extension methods for the ASP.NET MVC UrlHelper class
  /// </summary>
  public static class URLHelperExtensions
  {
    #region Details
    /// <summary>
    /// Generates the path to the details view for the given instance of a model class.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="modelInstance"></param>
    /// <param name="routeValues"></param>
    /// <returns></returns>
    /// <remarks>
    /// Alias for PathTo
    /// </remarks>
    public static string PathToDetails<TModel>(this UrlHelper helper, TModel modelInstance)
    {
      return PathTo(helper, modelInstance, null);
    }

    /// <summary>
    /// Generates the path to the details view for the given instance of a model class.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="modelInstance"></param>
    /// <param name="routeValues"></param>
    /// <returns></returns>
    /// <remarks>
    /// Alias for PathTo
    /// </remarks>
    public static string PathToDetails<TModel>(this UrlHelper helper, TModel modelInstance, object routeValues)
    {
      return PathTo(helper, modelInstance, routeValues);
    }

    /// <summary>
    /// Generates the path to the details view for the given instance of a model class.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="modelInstance"></param>
    /// <param name="routeValues"></param>
    /// <returns></returns>
    public static string PathTo<TModel>(this UrlHelper helper, TModel modelInstance)
    {
      return PathTo(helper, modelInstance, null);
    }

    /// <summary>
    /// Generates the path to the details view for the given instance of a model class.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="modelInstance"></param>
    /// <param name="routeValues"></param>
    /// <returns></returns>
    public static string PathTo<TModel>(this UrlHelper helper, TModel modelInstance, object routeValues)
    {
      int modelID;
      Type modelType = modelInstance.GetType();

      // Determine Action Name
      string actionName = null;

      // Check the class attributes for a custom details action name, otherwise assign a default name
      MVCHelperDetailsMethodNameAttribute detailsNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperDetailsMethodNameAttribute>(modelType);
      if (detailsNameAttribute != null)
      {
        actionName = detailsNameAttribute.DetailsMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.DETAILS_ACTION_NAME_DEFAULT;
      }

      // Find model instance ID
      modelID = MVCHelperUtilities.GetModelID<TModel>(modelInstance);

      RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
      routeValueDictionary.Add("id", modelID);

      // Find Controller name
      // If the controller name is null then the current controller will be used by default
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the path using UrlHelper's Action method
      string path = helper.Action(
          actionName,
          controllerName,
          routeValueDictionary
        );

      return path;
    }
    #endregion
    
    #region Index

    /// <summary>
    /// Determines the index path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// 
    /// <returns>HTML string with a hyperlink to the index page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.PathToIndex(typeof(Model)); %>
    /// ]]></code>
    /// </example>
    public static string PathToIndex(this UrlHelper helper, Type modelType)
    {
      return PathToIndex(helper, modelType, null);
    }

    /// <summary>
    /// Determines the index path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// 
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// 
    /// <returns>HTML string with a hyperlink to the index page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.PathToIndex(typeof(Model), new { new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string PathToIndex(this UrlHelper helper, Type modelType, object routeValues)
    {
      // Determine Action Name
      string actionName = null;

      MVCHelperIndexMethodNameAttribute indexNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperIndexMethodNameAttribute>(modelType);
      if (indexNameAttribute != null)
      {
        actionName = indexNameAttribute.IndexMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.INDEX_ACTION_NAME_DEFAULT;
      }

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the path using the UrlHelper Action method
      string path = helper.Action(actionName, controllerName, routeValues);

      return path;
    }

    /// <summary>
    /// Generates an HTML link to the index view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// 
    /// <returns>HTML string with a hyperlink to the index view for given model class</returns>
    /// <example>
    /// In an ASP.NET MVC view:
    /// <code><![CDATA[
    /// <%= HTML.PathToIndex<YourModelClass>(); %>
    /// ]]></code>
    /// </example>
    public static string PathToIndex<TModel>(this UrlHelper helper)
    {
      return PathToIndex(helper, typeof(TModel), null);
    }

    /// <summary>
    /// Generates an HTML link to the index view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// 
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns></returns>
    public static string PathToIndex<TModel>(this UrlHelper helper, object routeValues)
    {
      return PathToIndex(helper, typeof(TModel), routeValues);
    }

    #endregion

    #region Edit
    /// <summary>
    /// Generates HTML for a Path to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// 
    /// <param name="modelInstance">model instance</param>
    /// <returns>HTML string with a hyperPath to the edit view of the given model instance</returns>
    public static string PathToEdit<TModel>(this UrlHelper helper, TModel modelInstance)
    {
      return PathToEdit(helper, modelInstance, null, null);
    }

    /// <summary>
    /// Generates HTML for a Path to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// 
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Anonymous type containing additional route values to pass for URL generation.</param>
    /// <returns>HTML string with a hyperPath to the edit view of the given model instance</returns>
    public static string PathToEdit<TModel>(this UrlHelper helper, TModel modelInstance, object routeValues)
    {
      return PathToEdit(helper, modelInstance, routeValues, null);
    }

    /// <summary>
    /// Generates HTML for a Path to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// 
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Anonymous type containing additional route values to pass for URL generation.</param>
    /// <param name="HTMLAttributes">Anonymous type containing attribute value pairs to be applied to the Path tag that is returned</param>
    /// <returns>HTML string with a hyperPath to the edit view of the given model instance</returns>
    public static string PathToEdit<TModel>(this UrlHelper helper, TModel modelInstance, object routeValues, object HTMLAttributes)
    {
      Type modelType = modelInstance.GetType();

      // Get the name for the edit action for the given model type
      string editActionName;
      var editMethodNameAttribute = MVCHelperUtilities.GetClassAttribute<MVCHelperEditMethodNameAttribute>(modelType);
      if (editMethodNameAttribute != null)
        editActionName = editMethodNameAttribute.EditMethodName;
      else
        editActionName = MVCHelpersDefaultValues.EDIT_ACTION_NAME_DEFAULT;

      // Get the model instance id
      int modelID = MVCHelperUtilities.GetModelID(modelInstance);

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Add the model id to the given route values.  This requires converting the route values
      // which are an object of anonymous type
      RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
      routeValueDictionary.Add("id", modelID);

      return helper.Action(
        editActionName,
        controllerName,
        routeValueDictionary
      );
    }
    #endregion

    #region New
    
    /// <summary>
    /// Determines the New path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// 
    /// <returns>HTML string with a hyperPath to the New page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.PathToNew(typeof(Model), "click here"); %>
    /// ]]></code>
    /// </example>
    public static string PathToNew(this UrlHelper helper, Type modelType)
    {
      return PathToNew(helper, modelType, null);
    }

    /// <summary>
    /// Determines the New path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// 
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// 
    /// <returns>HTML string with a hyperPath to the New page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.PathToNew(typeof(Model), "click here", new { controller = "other_controller" }, new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string PathToNew(this UrlHelper helper, Type modelType, object routeValues)
    {
      // Determine Action Name
      string actionName = null;

      MVCHelperNewMethodNameAttribute NewNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperNewMethodNameAttribute>(modelType);
      if (NewNameAttribute != null)
      {
        actionName = NewNameAttribute.NewMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.NEW_ACTION_NAME_DEFAULT;
      }

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the HTML using an MVC built-in ActionPath method
      // TODO: Allow routeValues to override actionName and controllerName
      string PathHTML = helper.Action(actionName, controllerName, routeValues);

      return PathHTML;
    }

    /// <summary>
    /// Generates an HTML Path to the New view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// 
    /// <returns>HTML string with a hyperPath to the New view for given model class</returns>
    /// <example>
    /// In an ASP.NET MVC view:
    /// <code><![CDATA[
    /// <%= HTML.PathToNew<YourModelClass>("click here"); %>
    /// ]]></code>
    /// </example>
    public static string PathToNew<TModel>(this UrlHelper helper)
    {
      return PathToNew(helper, typeof(TModel), null);
    }

    /// <summary>
    /// Generates an HTML Path to the New view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// 
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns></returns>
    public static string PathToNew<TModel>(this UrlHelper helper, object routeValues)
    {
      return PathToNew(helper, typeof(TModel), routeValues);
    }


    #endregion
  }
}
